//
// Created by martin on 2/24/22.
//

#include "muduo/base/Thread.h"
#include "muduo/base/CurrentThread.h"
#include "muduo/base/Mutex.h"
#include "muduo/base/Atomic.h"
#include "muduo/base/Timestamp.h"
#include "muduo/base/BlockingQueue.h"

#include <map>
#include <vector>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

bool g_verbose = false;
muduo::MutexLock g_mutex;
muduo::AtomicInt32 g_count;
std::map<int, int> g_delays;

void threadFunc()
{
    g_count.increment();
}

void threadFunc2(muduo::Timestamp start)
{
    muduo::Timestamp now(muduo::Timestamp::now());
    int delay = static_cast<int>(muduo::timeDifference(now, start) * 1000000); // sec => usec
    muduo::MutexLockGuard lock(g_mutex);
    ++g_delays[delay];
}

void forkBench()
{
    sleep(10);
    muduo::Timestamp start(muduo::Timestamp::now());
    int kProcesses = 10*1000;

    printf("Creating %d processes in serial\n", kProcesses);
    for (int i = 0; i < kProcesses; ++i)
    {
        pid_t child = fork();
        if (child == 0)
        { // child
            exit(0);
        }
        else
        { // parent
            waitpid(child, NULL, 0);
        }
    }

    double timeUsed = muduo::timeDifference(muduo::Timestamp::now(), start);
    printf("time elapsed %.3f seconds, process creation time used %.3f us\n",
           timeUsed, timeUsed*1e6/kProcesses);
    printf("number of created processes %d\n", kProcesses);
}

class Bench
{
public:
    Bench(int numThreads)
    : startLatch_(numThreads),
      stopLatch_(1)
    {
        threads_.reserve(numThreads);
        for (int i = 0; i < numThreads; ++i) {
            char name[32];
            snprintf(name, sizeof(name), "work thread %d", i);
            threads_.emplace_back(new muduo::Thread(
                    [this] { threadFunc(); },
                    muduo::string(name)));
        }
    }

    void Start()
    {
        const int numThreads = static_cast<int>(threads_.size());
        printf("Creating %d thread in parallel\n", numThreads);
        muduo::Timestamp start = muduo::Timestamp::now();

        for (auto& thr : threads_)
        {
            thr->start();
        }
        startLatch_.wait();
        double timeused = muduo::timeDifference(muduo::Timestamp::now(), start);
        printf("all %d threads started, %.3fms total, %.3fus per thread\n",
               numThreads, 1e3 * timeused, 1e6 * timeused / numThreads);

        TimestampQueue::queue_type queue = start_.drain();
        if (g_verbose)
        {
            for (const auto& e : queue)
            {
                printf("thread %d, %.0f us\n", e.first, muduo::timeDifference(e.second, start) * 1e6);
            }
        }
    }

    void Stop()
    {
        muduo::Timestamp stop = muduo::Timestamp::now();
        stopLatch_.countDown();
        for (auto& thr : threads_)
        {
            thr->join();
        }

        muduo::Timestamp t2 = muduo::Timestamp::now();
        printf("all %zd threads joined, %.3fms\n",
               threads_.size(), 1e3 * muduo::timeDifference(t2, stop));
        TimestampQueue::queue_type queue = done_.drain();
        if (g_verbose)
        {
            for (const auto& e : queue)
            {
                printf("thread %d, %.0f us\n", e.first, muduo::timeDifference(e.second, stop) * 1e6);
            }
        }
    }

private:
    void threadFunc()
    {
        const int tid = muduo::CurrentThread::tid();
        start_.put(std::make_pair(tid, muduo::Timestamp::now()));
        startLatch_.countDown();
        stopLatch_.wait();
        done_.put(std::make_pair(tid, muduo::Timestamp::now()));
    }

    using TimestampQueue = muduo::BlockingQueue<std::pair<int, muduo::Timestamp>>;
    TimestampQueue start_, run_, done_;
    muduo::CountDownLatch startLatch_, stopLatch_;
    std::vector<std::unique_ptr<muduo::Thread>> threads_;
};

int main(int argc, char* argv[])
{
    g_verbose = argc > 1;
    printf("pid=%d, tid=%d, verbose=%d\n",
           ::getpid(), muduo::CurrentThread::tid(), g_verbose);
    muduo::Timestamp start(muduo::Timestamp::now());

    int kThreads = 100*1000;
    printf("Creating %d threads in serial\n", kThreads);
    for (int i = 0; i < kThreads; ++i)
    {
        muduo::Thread t1(threadFunc);
        t1.start();
        t1.join();
    }

    double timeUsed = muduo::timeDifference(muduo::Timestamp::now(), start);
    printf("elapsed %.3f seconds, thread creation time %.3f us per thread\n", timeUsed, timeUsed*1e6/kThreads);
    printf("number of created threads %d, g_count = %d\n", muduo::Thread::numCreated(), g_count.get());

    for (int i = 0; i < kThreads; ++i) {
        muduo::Timestamp now(muduo::Timestamp::now());
        muduo::Thread t2(std::bind(threadFunc2, now));
    }

    if (g_verbose)
    {
        muduo::MutexLockGuard lock(g_mutex);
        for (const auto& delay : g_delays)
        {
            printf("delay = %d, count = %d\n",
                   delay.first, delay.second);
        }
    }

    Bench t(1000);
    t.Start();
    t.Stop();

    forkBench();
    return 0;
}